<?php


namespace app\miniapp\controller;


use app\common\lib\exception\ApiException;
use think\Controller;
use think\Exception;
use think\Log;

class Common extends Controller
{
    /**
     * headers信息
     * @var array
     */
    public $headers = [];


    /**
     * 初始化的方法
     * @throws ApiException
     */
    protected function _initialize()
    {
        $this->checkRequestAuth();
    }

    /**
     * 检查每次app请求的数据是否合法
     * @throws ApiException
     */
    public function checkRequestAuth()
    {
        if (!defined('IS_ACCESS_TOKEN_CHECKED'))
        {
            // 首先需要获取headers
            $headers = request()->header();

            $appTypes = config('miniapp.app_types');

            if (!isset($headers['app-type']) || strpos($appTypes, $headers['app-type']) === false)
            {
                Log::write('当前设备不支持', 'error', true);
                throw new ApiException('当前设备不支持');
            }

            define('IS_ACCESS_TOKEN_CHECKED', true);
            // 将当前请求头信息存入header属性中，以便后面使用
            $this->headers = $headers;
        }
    }


    /**
     * 加密字符串
     * @param string $data 字符串
     * @return string
     */
    protected function encryptWithOpenssl($data): string
    {
        $config = config('miniapp');
        return base64_encode(openssl_encrypt($data, $config['aes_method'], $config['aes_key'], OPENSSL_RAW_DATA, $config['aes_iv']));
    }

    /**
     * 解密字符串
     * @param string $data 字符串
     * @return string
     */
    protected function decryptWithOpenssl($data): string
    {
        $config = config('miniapp');
        return openssl_decrypt(base64_decode($data), $config['aes_method'], $config['aes_key'], OPENSSL_RAW_DATA, $config['aes_iv']);
    }

    /**
     * 微信或者QQ，检验数据的真实性，并且获取解密后的.
     * @param $appId
     * @param $sessionKey
     * @param $encryptedData string 加密的用户数据
     * @param $iv string 与用户数据一同返回的初始向量
     * @return array | int 成功返回详细信息，失败返回false，例如：avatarUrl = "https://wx.qlogo.cn/mmopen/vi_32/DYAIOgq83epF3yvjxqJuRB9KydwTVHqEpia3wxwnBOkFUsDhMV9GwO9vcJ6tHlRbsEuicC9p7e859QCMVcRAt1uA/132"
     * city = "Chaoyang"
     * country = "China"
     * gender = {int} 1
     * language = "zh_CN"
     * nickName = "李柏楠"
     * openId = "oVpmX5KHiQXXabinAnBbYy3Um2ns"
     * province = "Beijing"
     * unionId = "oIVMdt71AOrtGBFSlYBe8zSDl5_o"
     * watermark = {array} [2]
     * appid = "wxa417c0a7a1e75643"
     * timestamp = {int} 1575437969
     */
    protected function wechatOrQQDecrypt($appId, $sessionKey, $encryptedData, $iv)
    {
        //qq登录没有unionid，有可能是我的测试号的原因，切换后再测试【解决：使用星座女神的qq号，登录qq开发平台进行的关联】
        if (strlen($sessionKey) !== 24)
        {
            return false;
        }
        $aesKey = base64_decode($sessionKey);
        if (strlen($iv) !== 24)
        {
            return false;
        }
        $aesIV = base64_decode($iv);
        $aesCipher = base64_decode($encryptedData);
        $result = openssl_decrypt($aesCipher, 'AES-128-CBC', $aesKey, 1, $aesIV);
        $data_arr = json_decode($result, true);
        if ($data_arr === NULL)
        {
            return false;
        }
        if ($data_arr['watermark']['appid'] !== $appId)
        {
            return false;
        }
        return $data_arr;
    }

    /**
     * 百度小程序 数据解密：低版本使用mcrypt库（PHP < 5.3.0），高版本使用openssl库（PHP >= 5.3.0）。
     *
     * @param string $ciphertext 待解密数据，返回的内容中的data字段
     * @param string $iv 加密向量，返回的内容中的iv字段
     * @param string $app_key 创建小程序时生成的app_key
     * @param string $session_key 登录的code换得的
     * @return string | false
     * headimgurl = "https://himg.bdimg.com/sys/portrait/item/be756d61726b6c69626e8042"
     * nickname = "百度网友ed3e9fc"
     * openid = "qsN6gzg9LLCWzbmoQIDh-7NovZ"
     * sex = {int} 0
     */
    protected function swanDecrypt($ciphertext, $iv, $app_key, $session_key)
    {
        $session_key = base64_decode($session_key);
        $iv = base64_decode($iv);
        $ciphertext = base64_decode($ciphertext);

        if (function_exists('openssl_decrypt'))
        {
            $plaintext = openssl_decrypt($ciphertext, 'AES-192-CBC', $session_key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING, $iv);
        } else
        {
            $td = mcrypt_module_open(MCRYPT_RIJNDAEL_128, null, MCRYPT_MODE_CBC, null);
            mcrypt_generic_init($td, $session_key, $iv);
            $plaintext = mdecrypt_generic($td, $ciphertext);
            mcrypt_generic_deinit($td);
            mcrypt_module_close($td);
        }
        if ($plaintext === false)
        {
            return false;
        }

        // trim pkcs#7 padding
        $pad = ord(substr($plaintext, -1));
        $pad = ($pad < 1 || $pad > 32) ? 0 : $pad;
        $plaintext = substr($plaintext, 0, strlen($plaintext) - $pad);

        // trim header
        $plaintext = substr($plaintext, 16);
        // get content length
        $unpack = unpack('Nlen/', substr($plaintext, 0, 4));
        // get content
        $content = substr($plaintext, 4, $unpack['len']);
        // get app_key
        $app_key_decode = substr($plaintext, $unpack['len'] + 4);

        return $app_key === $app_key_decode ? json_decode($content, true) : false;
    }


    /**
     * 今日头条小程序 验数据是否合法，并且解密数据 （解密参考： https://microapp.bytedance.com/dev/cn/mini-app/develop/open-capacity/user-information/sensitive-data-process）
     * @param $ciphertext
     * @param $iv
     * @param $session_key
     * @return bool|mixed
     * avatarUrl = "http://sf1-ttcdn-tos.pstatp.com/img/mosaic-legacy/3791/5070639578~120x256.image"
     * city = ""
     * country = "中国"
     * gender = {int} 0
     * language = ""
     * nickName = "zhunle"
     * openId = "2kfTH0Viw4tUSz6y"
     * province = ""
     * watermark = {array} [2]
     * appid = "tta48ad2373ba085a1"
     * timestamp = {int} 1575450447
     */
    protected function ttDecrypt($ciphertext, $iv, $session_key)
    {
        $session_key = base64_decode($session_key);
        $iv = base64_decode($iv);
        $ciphertext = base64_decode($ciphertext);

        if (function_exists('openssl_decrypt'))
        {
            $plaintext = openssl_decrypt($ciphertext, 'AES-128-CBC', $session_key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING, $iv);
        } else
        {
            $td = mcrypt_module_open(MCRYPT_RIJNDAEL_128, null, MCRYPT_MODE_CBC, null);
            mcrypt_generic_init($td, $session_key, $iv);
            $plaintext = mdecrypt_generic($td, $ciphertext);
            mcrypt_generic_deinit($td);
            mcrypt_module_close($td);
        }
        if ($plaintext === false)
        {
            return false;
        }

        // trim pkcs#7 padding
        $pad = ord(substr($plaintext, -1));
        $pad = ($pad < 1 || $pad > 32) ? 0 : $pad;
        $plaintext = substr($plaintext, 0, strlen($plaintext) - $pad);

        return json_decode(trim($plaintext), true);
    }


    /**
     * 获取session key
     * @param $code
     * @param string $env_type SWAN：百度小程序；WEAPP：微信小程序；QQ:qq小程序；TT：字节跳动小程序；
     * @param array $mini_item_conf 单个小程序的配置
     * @return string
     */
    protected function getSessionKey($code, $env_type = '', $mini_item_conf = []): string
    {
        try
        {
            switch ($env_type)
            {
                case 'SWAN':
                    $access_token_url = 'https://spapi.baidu.com/oauth/jscode2sessionkey?client_id=' . $mini_item_conf['app_key'] . '&sk=' . $mini_item_conf['app_secret'] . '&code=' . $code;
                    break;
                case 'WEAPP':
                    $access_token_url = 'https://api.weixin.qq.com/sns/jscode2session?appid=' . $mini_item_conf['app_id'] . '&secret=' . $mini_item_conf['app_secret'] . '&js_code=' . $code . '&grant_type=authorization_code';
                    break;
                case 'QQ':
                    $access_token_url = 'https://api.q.qq.com/sns/jscode2session?appid=' . $mini_item_conf['app_id'] . '&secret=' . $mini_item_conf['app_secret'] . '&js_code=' . $code . '&grant_type=authorization_code';
                    break;
                case 'TT':
                    $access_token_url = 'https://developer.toutiao.com/api/apps/jscode2session?appid=' . $mini_item_conf['app_id'] . '&secret=' . $mini_item_conf['app_secret'] . '&code=' . $code;
                    break;
            }
            if (!empty($access_token_url))
            {
                $result = file_get_contents($access_token_url);
                $result_arr = json_decode($result, true);
                if (isset($result_arr['session_key']))
                {
                    return $result_arr['session_key'];
                }
            }
        }
        catch (Exception $e)
        {
            Log::write('获取 ' . $env_type . ' session key异常，信息为：' . $e->getMessage() . PHP_EOL, 'error', true);
        }

        return '';
    }

    /**
     * 判断是否是微信浏览器
     */
    protected function isWexinBrowser(): bool
    {
        $userAgent = $this->request->header('user-agent');

        return strpos(strtolower($userAgent), 'micromessenger', 0);
    }

    /**
     * 通过code获取用户access_token和openid
     * @param array $wechatConfig
     * @param string $code
     * @return mixed|null
     */
    protected function wechatGetAccessTokenAndOpenidByCode(array $wechatConfig, string $code = '')
    {
        $resultData = null;
        try
        {
            $access_token_url = "https://api.weixin.qq.com/sns/oauth2/access_token?appid={$wechatConfig['appid']}&secret={$wechatConfig['secret']}&code={$code}&grant_type=authorization_code";

            if ($access_token_url)
            {
                $result = file_get_contents($access_token_url);

                //成功返回
                //{
                //     "access_token":"35_bIa5hTQdh2Eu8AVOw8zIalg_wudCdCErXJtRGqbhuVTk-mzca0Vzha_iQhZ-Sl1xlP6fu9PnkQz5beGb13-6ng",
                //     "expires_in":7200,
                //     "refresh_token":"35_4sd8r10q4JHyrE6R9oAY6GWUqIr52WcCQQNA88MwkSwVmb4F9cMbC1JM0ouOAVOU83NPtDhn-Q2oWdnGchyi-A",
                //     "openid":"oD2a60S82Ue72eKEQw97f7HCiHfY",
                //     "scope":"snsapi_base"
                // }
                $resultData = json_decode($result, true);
            }
        }
        catch (\Exception $e)
        {
            Log::write('获取 access_token 异常，信息为：' . $e->getMessage() . PHP_EOL, 'error', true);
        }

        return $resultData;
    }
}
